using System;
using CoreCalc;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine
{
    public class ToneMapOperator
    {
        public ImageFilmBase imageFilmBase;

        public ToneMapOperator(ImageFilmBase f)
        {
            imageFilmBase = f;
        }

        public virtual void ComputeImage()
        {
        }
    }


    public class LinearToneMap : ToneMapOperator
    {
        public float Scale;

        public LinearToneMap(ImageFilmBase f, float scale)
            : base(f)
        {
            Scale = scale;
        }

        public override void ComputeImage()
        {
            var pixelCount = imageFilmBase.Width * imageFilmBase.Height;
            var sampledPixels = imageFilmBase.pixelData;
            for (var j = 0; j < pixelCount; ++j)
            {
                if (imageFilmBase.SplatMode)
                {
                    RgbSpectrum ppx = RgbSpectrum.ZeroSpectrum();
                    SSE.ComputePixel(ref sampledPixels[j].Radiance, Scale, ref ppx, imageFilmBase.gammaTable,
                        ImageFilmBase.GAMMA_TABLE_SIZE);
                    imageFilmBase.SetPixel(j, ref ppx);
                }
                else
                {
                    if (!sampledPixels[j].Radiance.IsBlack())
                    {
                        imageFilmBase.ComputePixel(j, Scale);
                    }
                }
            }

        }
    }

    public class ReinhardToneMap : ToneMapOperator
    {
        public float PreScale;
        public float PostScale;
        public float Burn;
        SamplePixel[] xyzPixels;

        public ReinhardToneMap(ImageFilmBase f, float pres = 1f, float poss = 1.2f, float b = 0.75f)
            : base(f)
        {
            this.PreScale = pres;
            this.PostScale = poss;
            this.Burn = b;
        }

        public override void ComputeImage()
        {
            var pixelCount = imageFilmBase.Width * imageFilmBase.Height;
            var sampledPixels = imageFilmBase.pixelData;

            float alpha = .9f;
            //(Reinhard02ToneMapParams)this.toneMap;
            float preScale = PreScale;
            float postScale = PostScale;
            float burn = Burn;
            if (xyzPixels == null)
                xyzPixels = new SamplePixel[sampledPixels.Length];
            float Ywa = 0f;
            for (int ii = 0; ii < pixelCount; ++ii)
            {
                float weight = sampledPixels[ii].Weight;

                if (weight > 0f)
                {
                    float invWeight = 1f / weight;

                    RgbSpectrum rgb = sampledPixels[ii].Radiance * invWeight;

                    rgb.XYZ(out xyzPixels[ii].Radiance.c1, out xyzPixels[ii].Radiance.c2,
                        out xyzPixels[ii].Radiance.c3);
                    /*
                // Convert to XYZ color space
                xyzPixels[ii].Radiance.c1 = 0.412453f * rgb.c1 + 0.357580f * rgb.c2 + 0.180423f * rgb.c3;
                xyzPixels[ii].Radiance.c2 = 0.212671f * rgb.c1 + 0.715160f * rgb.c2 + 0.072169f * rgb.c3;
                xyzPixels[ii].Radiance.c3 = 0.019334f * rgb.c1 + 0.119193f * rgb.c2 + 0.950227f * rgb.c3;
                 */
                    xyzPixels[ii].Weight = weight;

                    // var xyz = pixelsRadiance[i].XYZ();

                    Ywa += xyzPixels[ii].Radiance.y();

                }
            }
            Ywa /= pixelCount;

            // Avoid division by zero
            if (Ywa.NearEqual(0f))
                Ywa = 1f;

            float Yw = preScale * alpha * burn;
            float invY2 = 1f / (Yw * Yw);
            float pScale = postScale * preScale * alpha / Ywa;

            for (int ii = 0; ii < pixelCount; ++ii)
            {
                var xyz = xyzPixels[ii].Radiance;

                float ys = xyz.c2;
                xyz *= pScale * (1f + ys * invY2) / (1f + ys);
                xyz.XYZToRGB();

                /*
            // Convert back to RGB color space
            sampledPixels[ii].Radiance.c1 = 3.240479f * xyz.c1 - 1.537150f * xyz.c2 - 0.498535f * xyz.c3;
            sampledPixels[ii].Radiance.c2 = -0.969256f * xyz.c1 + 1.875991f * xyz.c2 + 0.041556f * xyz.c3;
            sampledPixels[ii].Radiance.c3 = 0.055648f * xyz.c1 - 0.204043f * xyz.c2 + 1.057311f * xyz.c3;
                            
            this.pixelData[ii].Radiance = sampledPixels[ii].Radiance;
             */
                //pixelData[ii].Weight = 1.0f;

                /*
            this.frameBuffer.SetPixel(ii, this.Radiance2PixelFloat(sampledPixels[ii].radiance.c1),
                                     this.Radiance2PixelFloat(sampledPixels[ii].radiance.c2),
                                         * this.Radiance2PixelFloat(sampledPixels[ii].radiance.c3));*/
                //this.SetPixel(ii, ref xyz);

                imageFilmBase.ComputePixel(ii, ref xyz, 1.0f);
                /*
            pix = new RgbSpectrum(
                          this.Radiance2PixelFloat(sampledPixels[ii].Radiance.c1),
                          this.Radiance2PixelFloat(sampledPixels[ii].Radiance.c2),
                          this.Radiance2PixelFloat(sampledPixels[ii].Radiance.c3));
            this.SetPixel(ii, ref pix);*/
            }
        }
    }


    public class QuadricToneMap : ToneMapOperator
    {
        SamplePixel[] xyzPixels;

        public QuadricToneMap(ImageFilmBase f) : base(f)
        {
        }

        public override void ComputeImage()
        {
            var pixelCount = imageFilmBase.Width*imageFilmBase.Height;
            var sampledPixels = imageFilmBase.pixelData;
            float maxIntensity = 0f ,mean=0f, sqmean=0f;
            if (xyzPixels == null)
                xyzPixels = new SamplePixel[sampledPixels.Length];
            for (int ii = 0; ii < pixelCount; ++ii)
            {
                float weight = sampledPixels[ii].Weight;

                if (weight > 0f)
                {
                    float invWeight = 1f / weight;

                    RgbSpectrum rgb = sampledPixels[ii].Radiance * invWeight;

                    rgb.XYZ(out xyzPixels[ii].Radiance.c1, out xyzPixels[ii].Radiance.c2,
                        out xyzPixels[ii].Radiance.c3);
                    /*
                // Convert to XYZ color space
                xyzPixels[ii].Radiance.c1 = 0.412453f * rgb.c1 + 0.357580f * rgb.c2 + 0.180423f * rgb.c3;
                xyzPixels[ii].Radiance.c2 = 0.212671f * rgb.c1 + 0.715160f * rgb.c2 + 0.072169f * rgb.c3;
                xyzPixels[ii].Radiance.c3 = 0.019334f * rgb.c1 + 0.119193f * rgb.c2 + 0.950227f * rgb.c3;
                 */
                    xyzPixels[ii].Weight = weight;

                    // var xyz = pixelsRadiance[i].XYZ();

                    mean += xyzPixels[ii].Radiance.y();
                    sqmean += xyzPixels[ii].Radiance.y() * xyzPixels[ii].Radiance.y();


                }
            }
            mean/= pixelCount;
            sqmean /= pixelCount;
            float variance = sqmean - mean*mean;
            maxIntensity = mean+ MathLab.Sqrt(variance);
            for (int ii = 0; ii < pixelCount; ++ii)
            {
                var cie = xyzPixels[ii].Radiance;
                cie.c1 = (float)(Math.Log(cie.c1 / maxIntensity + 1.0f) / Math.Log(2.2f));
                cie.c2 = (float)(Math.Log(cie.c2 / maxIntensity + 1.0f) / Math.Log(2.2f));
                cie.c3 = (float)(Math.Log(cie.c3 / maxIntensity + 1.0f) / Math.Log(2.2f));
                imageFilmBase.ComputePixel(ii, ref cie, 1.0f); 
            }

        }
    }
}